package com.imooc.coupon.service.impl;

import com.google.common.base.Stopwatch;
import com.imooc.coupon.constant.SystemConstant;
import com.imooc.coupon.service.AsyncService;
import com.imooc.coupon.entity.CouponTemplate;
import com.imooc.coupon.repository.CouponTemplateRepo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 异步服务功能接口实现
 * Created By TINY
 * Create Time 2020/2/9 - 19:49
 **/
@Slf4j
@Service
public class AsyncServiceImpl implements AsyncService {

    /** CouponTemplate 持久层 */
    private final CouponTemplateRepo couponTemplateRepo;

    /** Redis 简化操作类 */
    private final StringRedisTemplate redisTemplate;

    @Autowired
    public AsyncServiceImpl(CouponTemplateRepo couponTemplateRepo,
                            StringRedisTemplate redisTemplate) {
        this.couponTemplateRepo = couponTemplateRepo;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 根据模板异步创建优惠券码
     *
     * @param template {@link CouponTemplate} 优惠券模板实体
     */
    @Async(value = "asyncExecutor")
    @Override
    public void asyncConstructCouponByTemplate(CouponTemplate template) {
        // 开始业务计时
        Stopwatch watch = Stopwatch.createStarted();

        Set<String> couponCodes = buildCouponCode(template);

        // 形如 imooc_coupon_template_code_1
        String redisKey = String.format("%s%s",
                SystemConstant.RedisPrefix.COUPON_TEMPLATE, template.getId());

        log.info("存储优惠券码到 Redis，数量：{} ",
                redisTemplate.opsForList().rightPushAll(redisKey, couponCodes));

        template.setAvailable(true); // 设置优惠券模板可用

        couponTemplateRepo.save(template); // 存储优惠券模板到数据库

        watch.stop(); // 停止计时

        log.info("通过模板构造优惠券码花费时间：{}ms", watch.elapsed(TimeUnit.MILLISECONDS));

        // TODO 优惠券生成完毕，执行发送邮件等操作
    }

    /**
     * 构建优惠券码
     * 每个优惠券码对应一张优惠券，位数为18位
     * 前四位： 产品线 + 类型
     * 中间六位： 日期时间
     * 后八位： 随机 0 ~ 9 构成的数字
     *
     * @param template {@link CouponTemplate} 优惠券模板实体类
     * @return 与template#count相同个数的优惠券码
     */
    private Set<String> buildCouponCode(CouponTemplate template) {

        // 开始任务计时
        Stopwatch watch = Stopwatch.createStarted();

        Set<String> result = new HashSet<>(template.getCount());

        String prefix4 = template.getProductLine().getCode().toString()
                + template.getCategory().getCode();

        String date = new SimpleDateFormat("yyMMdd")
                .format(template.getCreateTime());

        // 在此先用for循环生成优惠券码
        for (int i = 0; i != template.getCount(); ++i) {

            result.add(prefix4 + buildCouponCodeSuffix14(date));
        }

        // 由于for中生成的优惠券码有可能重复，从而导致优惠券码达不到指定个数，这里再使用
        // while循环逐步补齐；之所以不一开始就使用while循环主要是因为while每次都检查
        // Set中优惠码的个数，效率较低，结合使用可以提高效率
        while (result.size() < template.getCount()) {

            result.add(prefix4 + buildCouponCodeSuffix14(date));
        }

        // 断言生成的优惠券码的个数一定等于优惠券个数
        assert result.size() == template.getCount();

        watch.stop();  // 停止计时

        log.info("生成优惠券码花费时间: {}ms", watch.elapsed(TimeUnit.MILLISECONDS));

        return result;
    }

    /**
     * 构造优惠券码的后14位
     * @param date 创建优惠券码的日期
     * @return 14位优惠券码
     */
    private String buildCouponCodeSuffix14(String date) {

        char[] base = new char[]{'1', '2', '3', '4', '5', '6', '7', '8', '9'};

        // 中间六位
        List<Character> chars = date.chars()
                .mapToObj(v -> (char) v).collect(Collectors.toList());
        Collections.shuffle(chars);

        String mid6 = chars.stream()
                .map(Object::toString).collect(Collectors.joining());

        // 后八位
        String suffix8 = RandomStringUtils.random(1, base)
                + RandomStringUtils.randomNumeric(7);

        return mid6 + suffix8;
    }
}
